LLM训练主要包含以下几类计算操作:
除法操作及处理方式
处理方式:GPU没有专用除法单元,除法通过倒数近似 + 乘法实现。
使用快速倒数指令(如 __frcp_rn)计算 1/x,再与被除数相乘。
这些操作由 CUDA Core 完成,属于逐元素操作。
硬件分工总览
| 操作类型 | 执行单元 | 原因 |
|---|---|---|
| 矩阵乘法 (GEMM) | Tensor Core | 专为矩阵运算优化 |
| 激活函数 (GeLU/SiLU) | CUDA Core | 逐元素操作 |
| 梯度计算 (GEMM部分) | Tensor Core | 本质是矩阵乘法 |
| 梯度计算 (逐元素) | CUDA Core | 如激活函数导数 |
| 除法操作 | CUDA Core | 倒数近似 + 乘法实现 |
| 权重更新 (Adam等) | CUDA Core | 逐元素操作(含除法) |
| AllReduce通信 | CUDA Core + NVLink | 数据搬运+规约 |
| LayerNorm / Softmax | CUDA Core | 规约+逐元素+除法 |
前向传播详解
● 线性层 (占比最大 ~60-70%)
矩阵乘法由Tensor Core完成,偏置加法由CUDA Core完成
Y = XW + b● 激活函数 (GeLU/SiLU)
纯逐元素操作,涉及tanh、乘法等
GeLU(x) = x · Φ(x)● Attention计算
QK^T 和 ·V 由Tensor Core完成
Softmax/Scaling(含除法)由CUDA Core完成
● LayerNorm
规约求均值/方差 + 逐元素归一化(含1/σ除法)
(x - μ) / σ · γ + β反向传播详解
反向传播的梯度计算本质上也是矩阵乘法,因此主要由Tensor Core完成。
# 对于线性层 Y = XW 的反向传播 # 权重梯度 - Tensor Core (GEMM) dL/dW = XT · dL/dY # 输入梯度 - Tensor Core (GEMM) dL/dX = dL/dY · WT # 激活函数梯度 - CUDA Core (逐元素) dL/dx = dL/dy · f'(x)
权重更新 (Adam优化器)
优化器更新全部由CUDA Core完成,因为都是纯逐元素操作,包括多次除法运算。
# Adam优化器 - 全部由CUDA Core执行 m = β₁·m + (1-β₁)·g # 一阶动量 v = β₂·v + (1-β₂)·g² # 二阶动量 m̂ = m / (1-β₁t) # 偏置校正 (除法) v̂ = v / (1-β₂t) # 偏置校正 (除法) w = w - lr · m̂ / (√v̂ + ε) # 权重更新 (除法)
除法操作的GPU实现
GPU没有专用的除法硬件单元,所有除法操作通过倒数近似 + 乘法实现。
# GPU除法的底层实现原理 # 计算 a / b 的过程: reciprocal = __frcp_rn(b) # 快速倒数近似: 1/b result = a * reciprocal # 乘法得到最终结果 # 对于更高精度,使用牛顿迭代优化: x = __frcp_rn(b) # 初始近似 x = x * (2 - b * x) # 牛顿迭代1次 result = a * x
快速倒数的实现原理
快速倒数主要有两种实现方式:硬件查表法(GPU实际使用)和经典的位级技巧算法。
方法一:硬件查表法(GPU实际使用)
现代GPU内置查找表 (LUT),直接根据浮点数的指数和部分尾数查表得到近似倒数:
输入: b = 1.mantissa × 2^exp 输出: 1/b ≈ LUT[mantissa高位] × 2^(-exp-1)
硬件用几个时钟周期完成查表+插值,精度约12-13位有效位。
方法二:Fast Inverse Square Root(经典软件算法)
著名的"魔法数字"算法(Quake III引擎),利用IEEE 754浮点格式的位级技巧:
float fast_inv_sqrt(float x) { int i = *(int*)&x; // 浮点数按整数解释 i = 0x5f3759df - (i >> 1); // 魔法数字! float y = *(float*)&i; // 整数按浮点解释 y = y * (1.5f - 0.5f*x*y*y); // 牛顿迭代优化 return y; }
原理解析:
IEEE 754 单精度浮点数:(-1)^s × 1.M × 2^(E-127)
将其按整数 I 解释:I = E × 2²³ + M
对于 y = 1/√x,取对数:log(y) = -0.5 × log(x)
整数近似:I_y ≈ 1.5 × 2²³ × 127 - 0.5 × I_x
化简得:I_y = 0x5f3759df - (I_x >> 1)
两种方法对比
| 方法 | 使用场景 | 精度 | 速度 |
|---|---|---|---|
| 硬件LUT | GPU __frcp_rn |
~12位 | 4-8 cycles |
| 魔法数字 | CPU / 无硬件支持 | ~8位 | 极快 |
| 牛顿迭代 | 精度不够时补充 | 每次翻倍 | +4 cycles/次 |
GPU的 __frcp_rn 本质就是硬件查表 + 可选迭代的封装。
除法操作出处汇总
| 除法操作出处 | 具体形式 | 执行单元 |
|---|---|---|
| Softmax | exp(xᵢ) / Σexp(xⱼ) | CUDA Core |
| LayerNorm | (x - μ) / σ | CUDA Core |
| Attention Scaling | QK^T / √d_k | CUDA Core |
| Adam 偏置校正 | m / (1-β^t) | CUDA Core |
| Adam 更新 | m̂ / (√v̂ + ε) | CUDA Core |
为什么不直接用高精度除法?
| 方法 | 延迟 (cycles) | 说明 |
|---|---|---|
硬件除法 __fdiv_rn |
~20-36 | 完全精确 |
快速倒数 __frcp_rn |
~4-8 | 足够日常使用 |
| 倒数 + 1次牛顿迭代 | ~8-12 | 接近完全精度 |
深度学习对精度要求不高(FP16/BF16训练),快速倒数的精度已经足够,因此通常直接使用 __frcp_rn 省去迭代,以获得 3-5倍 的性能提升。
为什么Tensor Core占主导?
H100 PCIe 算力对比
LLM的计算特点:Transformer主体是GEMM操作;参数量越大,GEMM占比越高;逐元素操作(包括除法)虽然数量多,但计算量相对较小,主要瓶颈在于显存访问。
实际优化要点
Tensor Core利用率
矩阵维度对齐到8/16的倍数
CUDA Core操作
算子融合 (Fused Kernel)
内存带宽
Flash Attention、激活重计算
通信开销
计算-通信重叠、梯度压缩
常用术语全称
| 缩写 | 全称 | 说明 |
|---|---|---|
| FMA | Fused Multiply-Add | 融合乘加,单指令完成 D = A×B + C |
| GEMM | General Matrix Multiply | 通用矩阵乘法,深度学习核心操作 |
| GeLU | Gaussian Error Linear Unit | 高斯误差线性单元,Transformer常用激活函数 |
| SiLU | Sigmoid Linear Unit | 又称Swish,SiLU(x) = x × σ(x) |
算子融合与硬件的关系
算子融合是软件层面的优化策略,Tensor Core / CUDA Core是硬件执行单元。融合后的算子内部会同时调用两种硬件单元。
完整的层次关系
┌─────────────────────────────────────────────────┐ │ 算子融合 (软件层面决策) │ │ 决定哪些操作合并成一个 Kernel │ ├─────────────────────────────────────────────────┤ │ 融合后的算子 (一个 Kernel) │ │ __global__ void matmul_bias_gelu(...) │ ├─────────────────────────────────────────────────┤ │ Kernel 内部的硬件调度 │ │ ├── GEMM 部分 → Tensor Core 执行 │ │ └── Bias + GeLU 部分 → CUDA Core 执行 │ │ (中间结果在寄存器传递,不写回显存) │ ├─────────────────────────────────────────────────┤ │ 硬件: Tensor Core + CUDA Core │ └─────────────────────────────────────────────────┘
硬件级融合 vs 软件级融合
| 层次 | 名称 | 示例 | 执行单元 |
|---|---|---|---|
| 硬件级 | FMA 指令 | D = A×B + C(单条指令) | Tensor Core |
| 软件级 | 算子融合 | GEMM + Bias + GeLU → 一个 Kernel | Tensor Core + CUDA Core |
Tensor Core 的基本指令 D = A×B + C 是一种硬件级 FMA 融合;而我们常说的"算子融合"是软件级优化,将多个算子合并到一个 Kernel,让 Tensor Core 输出直接传给 CUDA Core 处理,中间结果留在寄存器/共享内存中。
算子融合代码示例
# 未融合: 3次 Kernel 调用,3次显存读写 y = linear(x) # Kernel 1: Tensor Core GEMM,写显存 y = y + bias # Kernel 2: CUDA Core 加法,读写显存 y = gelu(y) # Kernel 3: CUDA Core 激活,读写显存 # 融合后: 1次 Kernel 调用,1次显存写入 y = fused_linear_bias_gelu(x, weight, bias) # 内部流程: # 1. Tensor Core 做 GEMM → 结果留在寄存器 # 2. CUDA Core 做 bias + gelu → 结果写回显存
融合带来的性能收益
示例:Y = GeLU(X × W + b),矩阵大小 4096×4096
❌ 未融合 (3个算子)
MatMul: 读 64MB, 写 32MB
Add: 读 32MB, 写 32MB
GeLU: 读 32MB, 写 32MB
总计: 256MB 内存访问
✅ 融合后 (1个算子)
MatMul_Bias_GeLU:
读 64MB, 写 32MB
总计: 96MB 内存访问
内存访问减少 62%,性能提升 2-3x。这也是 Flash Attention 的核心优化思路之一。